package br.com.projeco.biblioteca.mensageria.config;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.reflections.Reflections;

import br.com.projeco.biblioteca.mensageria.mensagem.MensagemBase;
import br.com.projeco.biblioteca.mensageria.servico.IServicoBase;
import br.com.projeco.biblioteca.persistencia.EntidadeDb;

public final class ResolutorTipo {

	// Mapa de tipos conhecidos
	private Map<String, Class<?>> mapaTiposConhecidos;

	private Map<String, Class<?>> mapaServicosConhecidos;
	
	private Map<String, Class<?>> mapaDbLibsConhecidos;

	// Singleton
	private static ResolutorTipo instancia;

	private ResolutorTipo(Set<String> pacotesConhecidos)
			throws ResolucaoTipoException {
		try {

			// Inicializo os mapas
			mapaTiposConhecidos = new HashMap<String, Class<?>>();
			mapaServicosConhecidos = new HashMap<String, Class<?>>();
			mapaDbLibsConhecidos = new HashMap<String, Class<?>>();
			
			// Obtenho todas mensagens que extendem de mensagemBase
			// a partir da lista de pacote
			for (String nomeCompletoPacote : pacotesConhecidos) {

				Reflections reflections = new Reflections(nomeCompletoPacote);

				Set<Class<? extends MensagemBase>> subTypesMensagem = reflections
						.getSubTypesOf(MensagemBase.class);

				for (Class<? extends MensagemBase> mensagem : subTypesMensagem) {
					mapaTiposConhecidos.put(mensagem.getSimpleName(), mensagem);
				}
				
				Set<Class<? extends EntidadeDb>> subTypesDbLib = reflections
						.getSubTypesOf(EntidadeDb.class);
				
				for (Class<? extends EntidadeDb> mensagem : subTypesDbLib) {
					mapaDbLibsConhecidos.put(mensagem.getSimpleName(), mensagem);
				}
				
				Set<Class<? extends IServicoBase>> subTypesServico = reflections
						.getSubTypesOf(IServicoBase.class);

				for (Class<? extends IServicoBase> servico : subTypesServico) {

					if (servico.isInterface())
						mapaServicosConhecidos.put(servico.getSimpleName(),
								servico);
				}
			}

		} catch (Exception e) {
			throw new ResolucaoTipoException(e);
		}

	}

	/**
	 * Obt�m uma inst�ncia
	 * 
	 * @return Inst�ncia de <code>ResolutorTipo</code>.
	 * @throws ResolucaoTipoException
	 *             Caso ocorra algum problema durante a resolu��o.
	 */
	public static ResolutorTipo getInstance() throws ResolucaoTipoException {
		if (instancia == null) {
			throw new ResolucaoTipoException(
					"Erro: Resolutor de Tipo deve ser inicializado com as configura��es de pacote(s).");
		}
		return instancia;
	}

	/**
	 * Obt�m uma inst�ncia.
	 * 
	 * @return Inst�ncia de <code>ResolutorTipo</code>.
	 * @throws ResolucaoTipoException
	 *             Caso ocorra algum problema durante a resolu��o.
	 */
	public static ResolutorTipo getInstance(Set<String> pacotesConhecidos)
			throws ResolucaoTipoException {
		if (instancia == null) {
			instancia = new ResolutorTipo(pacotesConhecidos);
		}
		return instancia;
	}

	/**
	 * Adquire uma classe baseada no seu nome simples.
	 * 
	 * @param nomeSimplesClasse
	 *            Nome da classe.
	 * @return Classe resolvida.
	 * @throws ResolucaoTipoException
	 *             Caso ocorra algum problema durante a resolu��o.
	 */
	public static Class<?> get(String nomeSimplesClasse)
			throws ResolucaoTipoException {
		// Garanto a exist�ncia de uma inst�ncia
		ResolutorTipo instancia = getInstance();
		// Se n�o conhecer o tipo, lan�o exce��o rumtime
		if (!instancia.mapaTiposConhecidos.containsKey(nomeSimplesClasse)) {
			ClassNotFoundException cnfe = new ClassNotFoundException(
					"A classe '" + nomeSimplesClasse
							+ "' n�o � um tipo conhecido.");
			throw new ResolucaoTipoException(cnfe);
		}
		return instancia.mapaTiposConhecidos.get(nomeSimplesClasse);
	}

	public static Class<?> getService(String nomeSimplesClasse)
			throws ResolucaoTipoException {
		// Garanto a exist�ncia de uma inst�ncia
		ResolutorTipo instancia = getInstance();
		// Se n�o conhecer o tipo, lan�o exce��o rumtime
		if (!instancia.mapaServicosConhecidos.containsKey(nomeSimplesClasse)) {
			ClassNotFoundException cnfe = new ClassNotFoundException(
					"A classe '" + nomeSimplesClasse
							+ "' n�o � um tipo conhecido.");
			throw new ResolucaoTipoException(cnfe);
		}

		return instancia.mapaServicosConhecidos.get(nomeSimplesClasse);
	}
	
	public static Class<?> getDbLib(String nomeSimplesClasse)
			throws ResolucaoTipoException {
		// Garanto a exist�ncia de uma inst�ncia
		ResolutorTipo instancia = getInstance();
		// Se n�o conhecer o tipo, lan�o exce��o rumtime
		if (!instancia.mapaDbLibsConhecidos.containsKey(nomeSimplesClasse)) {
			ClassNotFoundException cnfe = new ClassNotFoundException(
					"A classe '" + nomeSimplesClasse
							+ "' n�o � um tipo conhecido.");
			throw new ResolucaoTipoException(cnfe);
		}

		return instancia.mapaDbLibsConhecidos.get(nomeSimplesClasse);
	}

	public Map<String, Class<?>> getMapaServicosConhecidos() {
		return mapaServicosConhecidos;
	}
	
	public Map<String, Class<?>> getMapaDbLibsConhecidos() {
		return mapaDbLibsConhecidos;
	}
}
